
local alien = require 'alien'
local struct = require 'alien.struct'
local core = require 'alien.core'
local ipairs, unpack, setmetatable, error
    = ipairs, unpack, setmetatable, error
local ceil
    = math.ceil

module (...)

size_t = 'uint'
--[[
if (alien.sizeof('pointer') == alien.sizeof('uint')) then
  size_t = 'uint'
elseif (alien.sizeof('pointer') == alien.sizeof('ulong')) then
  size_t = 'ulong'
elseif (alien.sizeof('pointer') == alien.sizeof('ushort')) then
  size_t = 'ushort'
else
  error('unable to find size_t')
end
--]]

local function struct_new(s_proto, ptr)
  local buf = core.buffer(ptr or s_proto.size)
  local function struct_get(_, key)
    if s_proto.offsets[key] then
      return buf:get(s_proto.offsets[key] + 1, s_proto.types[key])
    else
      error("field " .. key .. " does not exist")
    end
  end
  local function struct_set(_, key, val)
    if s_proto.offsets[key] then
      buf:set(s_proto.offsets[key] + 1, val, s_proto.types[key])
    else
      error("field " .. key .. " does not exist")
    end
  end
  return setmetatable({}, { __index = struct_get, __newindex = struct_set,
			    __call = function (self,newbuf) if newbuf then  buf = newbuf;  end return buf end })
end

local function struct_byval(s_proto)
  local types = {}
  local size = s_proto.size
  for i = 0, size - 1, 4 do
    if size - i == 1 then
      types[#types + 1] = "char"
    elseif size - i == 2 then
      types[#types + 1] = "short"
    else
      types[#types + 1] = "int"
    end
  end
  return unpack(types)
end

local function struct_cast(s_proto, str)
  local buf = str()
  local function struct_get(_, key)
    if s_proto.offsets[key] then
      return buf:get(s_proto.offsets[key] + 1, s_proto.types[key])
    else
      error("field " .. key .. " does not exist")
    end
  end
  local function struct_set(_, key, val)
    if s_proto.offsets[key] then
      buf:set(s_proto.offsets[key] + 1, val, s_proto.types[key])
    else
      error("field " .. key .. " does not exist")
    end
  end
  return setmetatable({}, { __index = struct_get, __newindex = struct_set,
			    __call = function () return buf end })
end

function defstruct(t)
  local off = 0
  local names, offsets, types = {}, {}, {}
  for _, field in ipairs(t) do
    local name, type = field[1], field[2]
    names[#names + 1] = name
    off = ceil(off / core.align(type)) * core.align(type)
    offsets[name] = off
    types[name] = type
    off = off + core.sizeof(type)
  end
  return { names = names, offsets = offsets, types = types, size = off, new = struct_new, cast = struct_cast,
	    byval = struct_byval }
end

QWORD = "double"

if (struct.pack('<I4', 1) == struct.pack('I4', 1)) then
  endian = "low"
else
  endian = "high"
end

if (endian == "low") then

  local buf = alien.buffer(alien.sizeof('double'))

  function double2lohi(dbl)
    buf:set(1, dbl, "double")
    return buf:get(1, "uint"), buf:get(5, "uint")
  end
  
  function lohi2double(lo, hi)
    buf:set(1, lo, "uint")
    buf:set(5, hi, "uint")
    return buf:get(1, "double")
  end
  
else

  local buf = alien.buffer(alien.sizeof('double'))

  function double2lohi(dbl)
    buf:set(1, dbl, "double")
    return buf:get(5, "uint"), buf:get(1, "uint")
  end
  
  function lohi2double(lo, hi)
    buf:set(5, lo, "uint")
    buf:set(1, hi, "uint")
    return buf:get(1, "double")
  end
  
end
  